﻿using System;
using System.Linq;
using System.Threading;

namespace RFIDMifare
{
    /// <summary>
    /// RFID的专属返回值类型
    /// </summary>
    public struct Mifare522Result
    {
        /// <summary>
        /// 执行状态
        /// </summary>
        public bool Status { get; set; }
        /// <summary>
        /// 返回数据的长度
        /// </summary>
        public int InfoLength { get; set; }
        /// <summary>
        /// 返回的数据
        /// </summary>
        public byte[] Info { get; set; }
        /// <summary>
        /// 返回的值数据
        /// </summary>
        public int Value { get; set; }
        /// <summary>
        /// 返回发送的指令
        /// </summary>
        public byte[] SendInfo { get; set; }
        /// <summary>
        /// 返回接受到的指令
        /// </summary>
        public byte[] ResponseInfo { get; set; }
    }

    public class RFIDMifare522
    {
        #region 枚举
        /// <summary>
        /// 请求模式
        /// </summary>
        public enum RequestMode
        {
            Idle = 0x26,
            All = 0x52
        }

        /// <summary>
        /// 命令类型
        /// </summary>
        private enum CommandType
        {
            /// <summary>
            /// 请求命令
            /// </summary>
            Request = 0x41,
            /// <summary>
            /// 防止碰撞
            /// </summary>
            AntiCollision = 0x42,
            /// <summary>
            /// 选择
            /// </summary>
            Select = 0x43,
            /// <summary>
            /// 暂停
            /// </summary>
            Halt = 0x44,
            /// <summary>
            /// 验证密码
            /// </summary>
            AuthKey = 0x46,
            /// <summary>
            /// 读数据块数据
            /// </summary>
            ReadDataBlock = 0x47,
            /// <summary>
            /// 写数据块数据
            /// </summary>
            WriteDataBlock = 0x48,
            /// <summary>
            /// 值操作
            /// </summary>
            ValueOperation = 0x4A
        }

        /// <summary>
        /// 密钥AB
        /// </summary>
        public enum KeyAB
        {
            A = 0x60,
            B = 0x61
        }

        /// <summary>
        /// 值操作模式
        /// </summary>
        public enum ValueMode
        {
            /// <summary>
            /// 增加
            /// </summary>
            Increase = 0xC1,
            /// <summary>
            /// 减少
            /// </summary>
            Decrease = 0xC0
        }

        /// <summary>
        /// 防碰撞等级
        /// </summary>
        public enum AntiCollisionLevel
        {
            Level1 = 0x93,
            Level2 = 0x95,
            Level3 = 0x97
        }
        #endregion

        #region 命令常量
        /// <summary>
        /// 结束符
        /// </summary>
        private static readonly byte CommandExitCode = 0x03;
        /// <summary>
        /// 命令头
        /// </summary>
        private static readonly byte[] PiccRequestCodeHeader = { 0x07, 0x02, Convert.ToByte(CommandType.Request), 0x01, 0x00, 0x00, CommandExitCode };
        private static readonly byte[] AntiCollisionCodeHeader = { 0x08, 0x02, Convert.ToByte(CommandType.AntiCollision), 0x02, 0x00, 0x00, 0x00, CommandExitCode };
        private static readonly byte[] SelectCodeHeader = { 0x0B, 0x02, Convert.ToByte(CommandType.Select), 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, CommandExitCode };
        private static readonly byte[] HaltCodeHeader = { 0x06, 0x02, Convert.ToByte(CommandType.Halt), 0x00, 0x00, CommandExitCode };
        private static readonly byte[] AuthenticationKeyCodeHeader = { 0x12, 0x02, Convert.ToByte(CommandType.AuthKey), 0x0C };
        private static readonly byte[] ReadDataBlockCodeHeader = { 0x07, 0x02, Convert.ToByte(CommandType.ReadDataBlock), 0x01, 0x00, 0x00, CommandExitCode };
        private static readonly byte[] WriteDataBlockCodeHeader = { 0x17, 0x02, Convert.ToByte(CommandType.WriteDataBlock), 0x11 };
        private static readonly byte[] ValueOperationCodeHeader = { 0x0D, 0x02, Convert.ToByte(CommandType.ValueOperation), 0x07 };

        #endregion

        #region 内部变量
        /// <summary>
        /// 命令请求的等待时间
        /// </summary>
        private readonly int waitTime = 200;
        public SerialPortManager serialPortManager = new SerialPortManager();
        private AutoResetEvent requestResponse = new AutoResetEvent(false);
        private Mifare522Result currentResult = new Mifare522Result();
        #endregion

        #region 属性
        /// <summary>
        /// 串口号
        /// </summary>
        public string PortName
        {
            get { return serialPortManager.PortName; }
            set { serialPortManager.PortName = value; }

        }
        #endregion

        #region 构造方法
        public RFIDMifare522()
        {
            serialPortManager.CurrentTransmissionType = SerialPortManager.TransmissionType.Hex;
            serialPortManager.SerialPortDataReceived += SerialPortManager_SerialPortDataReceived;
            serialPortManager.BaudRate = "9600";    //波特率
            serialPortManager.DataBits = "8";       //数据位
        }
        #endregion

        #region 串口相关方法
        /// <summary>
        /// 打开串口
        /// </summary>
        public void Open()
        {
            serialPortManager.Open();
        }

        /// <summary>
        /// 关闭串口
        /// </summary>
        public void Close()
        {
            serialPortManager.Close();
        }
        #endregion

        #region 卡操作相关方法
        /// <summary>
        /// 请求
        /// </summary>
        /// <param name="mode">请求模式</param>
        /// <returns></returns>
        public Mifare522Result PiccRequest(RequestMode mode = RequestMode.All)
        {
            //拼接请求命令
            //byte[] cmd = new byte[PiccRequestCodeHeader.Length];
            byte[] cmd = new byte[PiccRequestCodeHeader[0]];
            PiccRequestCodeHeader.CopyTo(cmd, 0);
            cmd[4] = Convert.ToByte(mode);  //设置请求模式
            cmd[cmd.Length - 2] = RFIDMifare522Utils.BCCCodeCalculation(cmd.ToArray(), cmd.Length - 2);   //计算BCC码

            //设置当前命令类型：请求命令
            //currentCmdType = CommandType.Request;

            //串口发送请求命令
            serialPortManager.WriteData(RFIDMifare522Utils.ByteArrayToString(cmd));
            // 返回发送的指令
            currentResult.SendInfo = cmd; 
            Console.WriteLine(BitConverter.ToString(currentResult.SendInfo));
            
            ClearResult();

            //等待回应
            requestResponse.WaitOne(waitTime);

            return currentResult;
        }

        /// <summary>
        /// 防碰撞
        /// 必须在[请求]之后使用才有效
        /// </summary>
        /// <param name="level">防碰撞等级</param>
        /// <returns></returns>
        public Mifare522Result AntiCollision(AntiCollisionLevel level = AntiCollisionLevel.Level1)
        {
            //拼接防碰撞命令
            //byte[] cmd = new byte[AntiCollisionCodeHeader.Length];
            byte[] cmd = new byte[AntiCollisionCodeHeader[0]];
            AntiCollisionCodeHeader.CopyTo(cmd, 0);
            cmd[4] = Convert.ToByte(level);
            cmd[cmd.Length - 2] = RFIDMifare522Utils.BCCCodeCalculation(cmd.ToArray(), cmd.Length - 2);   //计算BCC码

            //设置当前命令类型：请求命令
            //currentCmdType = CommandType.AntiCollision;

            //串口发送防碰撞命令
            serialPortManager.WriteData(RFIDMifare522Utils.ByteArrayToString(cmd));
            // 返回发送的指令
            currentResult.SendInfo = cmd;
            ClearResult();

            //等待响应
            requestResponse.WaitOne(waitTime);

            return currentResult;
        }

        /// <summary>
        /// 选择
        /// 必须在[请求]、[防碰撞]之后使用才有效
        /// </summary>
        /// <param name="level">防碰撞等级</param>
        /// <param name="cardID">卡号</param>
        /// <returns></returns>
        public Mifare522Result Select(byte[] cardID, AntiCollisionLevel level = AntiCollisionLevel.Level1)
        {
            if (cardID.Length != 4)
            {
                throw new ArgumentException("cardID只能是4位数");
            }

            //拼接选择命令
            //byte[] cmd = new byte[SelectCodeHeader.Length];
            byte[] cmd = new byte[SelectCodeHeader[0]];
            SelectCodeHeader.CopyTo(cmd, 0);    //设置选择命令
            cmd[4] = Convert.ToByte(level);     //设置防碰撞等级
            for (int i = 0; i < 4; i++)         //赋值卡号
            {
                cmd[i + 5] = cardID[i];
            }
            cmd[cmd.Length - 2] = RFIDMifare522Utils.BCCCodeCalculation(cmd.ToArray(), cmd.Length - 2);   //计算BCC码

            //设置当前命令类型：选择命令
            //currentCmdType = CommandType.Select;

            //串口发送选择命令
            serialPortManager.WriteData(RFIDMifare522Utils.ByteArrayToString(cmd));
            // 返回发送的指令
            currentResult.SendInfo = cmd;
            ClearResult();

            //等待响应
            requestResponse.WaitOne(waitTime);

            return currentResult;
        }

        /// <summary>
        /// 暂停
        /// </summary>
        /// <returns></returns>
        public Mifare522Result Halt()
        {
            //拼接暂停命令
            //byte[] cmd = new byte[HaltCodeHeader.Length];
            byte[] cmd = new byte[HaltCodeHeader[0]];
            HaltCodeHeader.CopyTo(cmd, 0);    //设置暂停命令
            cmd[cmd.Length - 2] = RFIDMifare522Utils.BCCCodeCalculation(cmd.ToArray(), cmd.Length - 2);   //计算BCC码

            //设置当前命令类型：暂停命令
            //currentCmdType = CommandType.Halt;

            //串口发送选择命令
            serialPortManager.WriteData(RFIDMifare522Utils.ByteArrayToString(cmd));
            // 返回发送的指令
            currentResult.SendInfo = cmd;
            ClearResult();

            //等待响应
            requestResponse.WaitOne(waitTime);

            return currentResult;
        }

        /// <summary>
        /// 密钥验证
        /// 
        /// S50卡分四个扇区
        /// 每个扇区有四大块
        /// 每块16字节
        /// 
        /// 特殊的：
        /// 第一个扇区的第一个数据块为卡号 只能读
        /// 每个扇区的最后一块为密钥块
        /// 密钥块左边保存密钥A 右边保存密钥B 中间保存存取控制（存取控制决定了密钥A/B的用途）
        /// 
        /// </summary>
        /// <param name="cardID"></param>
        /// <param name="key"></param>
        /// <param name="block"></param>
        /// <param name="keyAB"></param>
        /// <returns></returns>
        public Mifare522Result AuthenticationKey(byte[] cardID, byte[] key, int block, KeyAB keyAB = KeyAB.A)
        {
            if (!(0 <= block && block <= 63))
            {

                throw new ArgumentOutOfRangeException("区间只能是0~63");
            }

            //byte[] cmd = new byte[18];
            byte[] cmd = new byte[AuthenticationKeyCodeHeader[0]];

            //拼接命令
            AuthenticationKeyCodeHeader.CopyTo(cmd, 0);
            cmd[4] = Convert.ToByte(keyAB); //设置密钥AB

            for (int i = 0; i < 4; i++)     //设置卡号
            {
                cmd[5 + i] = cardID[i];
            }

            for (int i = 0; i < 6; i++)     //设置密钥
            {
                cmd[9 + i] = key[i];
            }

            cmd[cmd.Length - 3] = Convert.ToByte(block);    //设置要验证的卡块号
            cmd[cmd.Length - 2] = RFIDMifare522Utils.BCCCodeCalculation(cmd, cmd.Length - 2);   //计算BCC
            cmd[cmd.Length - 1] = CommandExitCode;  //添加结束编码

            //串口发送命令
            serialPortManager.WriteData(RFIDMifare522Utils.ByteArrayToString(cmd));
            // 返回发送的指令
            currentResult.SendInfo = cmd;
            ClearResult();

            //等待响应
            requestResponse.WaitOne(waitTime);

            return currentResult;
        }

        /// <summary>
        /// 读块数据
        /// 对卡内某一块进行验证成功后，即可对同一扇区的各个进行写操作（只要访问条件允许），
        /// 其中包括位于扇区尾的密码块，这是更改密码的唯一方法
        /// </summary>
        /// <param name="block"></param>
        /// <returns></returns>
        public Mifare522Result ReadDataBlock(int block)
        {
            if (!(0 <= block && block <= 63))
            {
                throw new ArgumentOutOfRangeException("区间只能是0~63");
            }

            //byte[] cmd = new byte[ReadDataBlockCodeHeader.Length];
            byte[] cmd = new byte[ReadDataBlockCodeHeader[0]];
            //拼接命令
            ReadDataBlockCodeHeader.CopyTo(cmd, 0);
            cmd[4] = Convert.ToByte(block);         //设置要读取的数据块
            cmd[cmd.Length - 2] = RFIDMifare522Utils.BCCCodeCalculation(cmd, cmd.Length - 2);   //设置BCC码

            //串口发送命令
            serialPortManager.WriteData(RFIDMifare522Utils.ByteArrayToString(cmd));
            // 返回发送的指令
            currentResult.SendInfo = cmd;
            ClearResult();

            //等待响应
            requestResponse.WaitOne(waitTime);

            return currentResult;
        }

        /// <summary>
        /// 写块数据
        /// 对卡内某一块进行验证成功后，即可对同一扇区的各个进行写操作（只要访问条件允许），
        /// 其中包括位于扇区尾的密码块，这是更改密码的唯一方法
        /// </summary>
        /// <param name="block"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public Mifare522Result WriteDataBlock(int block, byte[] data)
        {
            if (!(0 <= block && block <= 63))
            {
                throw new ArgumentOutOfRangeException("block区间只能是0~63");
            }
            if (data.Length != 16)
            {
                throw new ArgumentException("data必须包含16个数值");
            }

            //byte[] cmd = new byte[23];
            byte[] cmd = new byte[WriteDataBlockCodeHeader[0]];
            //拼接命令
            WriteDataBlockCodeHeader.CopyTo(cmd, 0);
            cmd[4] = Convert.ToByte(block);     //设置要写入数据块编号
            for (int i = 0; i < 16; i++)        //设置要写入的数据
            {
                cmd[5 + i] = data[i];
            }
            cmd[cmd.Length - 2] = RFIDMifare522Utils.BCCCodeCalculation(cmd, cmd.Length - 2);   //设置BCC码
            cmd[cmd.Length - 1] = CommandExitCode;  //结束码

            //串口发送命令
            serialPortManager.WriteData(RFIDMifare522Utils.ByteArrayToString(cmd));
            // 返回发送的指令
            currentResult.SendInfo = cmd;
            ClearResult();

            //等待响应
            requestResponse.WaitOne(waitTime);

            return currentResult;
        }

        /// <summary>
        /// 值操作（对某个块区的值增加/减少）
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="block"></param>
        /// <param name="value">要操作的值（低位在先）</param>
        /// <param name="saveBlock"></param>
        /// <returns></returns>
        public Mifare522Result ValueOperation(ValueMode mode, int block, byte[] value, int saveBlock)
        {
            if (!(0 <= block && block <= 63))
            {
                throw new ArgumentOutOfRangeException("block区间只能是0~63");
            }
            if (!(0 <= saveBlock && block <= saveBlock))
            {
                throw new ArgumentOutOfRangeException("block区间只能是0~63");
            }
            if (value.Length != 4)
            {
                throw new ArgumentException("value必须为4位数");
            }

            byte[] cmd = new byte[ValueOperationCodeHeader[0]];
            //拼接命令
            ValueOperationCodeHeader.CopyTo(cmd, 0);
            cmd[4] = Convert.ToByte(mode);  //设置操作方式 - 增加/删除
            cmd[5] = Convert.ToByte(block); //设置操作的块号
            for (int i = 0; i < value.Length; i++)  //设置操作值
            {
                cmd[6 + i] = value[i];
            }
            cmd[cmd.Length - 3] = Convert.ToByte(saveBlock);    //保存到的块号
            cmd[cmd.Length - 2] = RFIDMifare522Utils.BCCCodeCalculation(cmd, cmd.Length - 2);   //设置BCC码
            cmd[cmd.Length - 1] = CommandExitCode;  //结束码


            //串口发送命令
            serialPortManager.WriteData(RFIDMifare522Utils.ByteArrayToString(cmd));
            // 返回发送的指令
            currentResult.SendInfo = cmd;
            ClearResult();

            //等待响应
            requestResponse.WaitOne(waitTime);

            return currentResult;
        }

        /// <summary>
        /// 值操作（对某个块区的值增加/减少）
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="block"></param>
        /// <param name="value">要操作的值（低位在先）</param>
        /// <param name="saveBlock"></param>
        /// <returns></returns>
        public Mifare522Result ValueOperation(ValueMode mode, int block, int value, int saveBlock)
        {
            if (!(0 <= block && block <= 63))
            {
                throw new ArgumentOutOfRangeException("block区间只能是0~63");
            }
            if (!(0 <= saveBlock && block <= saveBlock))
            {
                throw new ArgumentOutOfRangeException("block区间只能是0~63");
            }

            byte[] cmd = new byte[ValueOperationCodeHeader[0]];
            //拼接命令
            ValueOperationCodeHeader.CopyTo(cmd, 0);
            cmd[4] = Convert.ToByte(mode);  //设置操作方式 - 增加/删除
            cmd[5] = Convert.ToByte(block); //设置操作的块号
            byte[] valueBytes = RFIDMifare522Utils.ValueTo4Byte(value);
            for (int i = 0; i < valueBytes.Length; i++)  //设置操作值
            {
                cmd[6 + i] = valueBytes[i];
            }
            cmd[cmd.Length - 3] = Convert.ToByte(saveBlock);    //保存到的块号
            cmd[cmd.Length - 2] = RFIDMifare522Utils.BCCCodeCalculation(cmd, cmd.Length - 2);   //设置BCC码
            cmd[cmd.Length - 1] = CommandExitCode;  //结束码


            //串口发送命令
            serialPortManager.WriteData(RFIDMifare522Utils.ByteArrayToString(cmd));
            // 返回发送的指令
            currentResult.SendInfo = cmd;
            ClearResult();

            //等待响应
            requestResponse.WaitOne(waitTime);

            return currentResult;
        }

        /// <summary>
        /// 写值操作
        /// </summary>
        /// <param name="block"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public Mifare522Result WriteValue(int block, int value)
        {

            //一个数据块对应一个数值
            //左到右 = 低位到高位
            //一个数值其实只占4个字节
            //但是数值要保存三次 从左往右存 第一次源码(0-3字节) 第二次取反(4-7字节) 第三次源码(8-11字节)
            //剩下4字节用于保存一个块号，表示备份数据的地址，这剩下的4字节中保存四次块号 第一次原块号 第二次取反 第三次原块号 第四次取反
            //如果没有备份地址就写当前的块号

            byte[] values = RFIDMifare522Utils.ValueToBlockData(value, block);

            return WriteDataBlock(block, values);
        }

        public Mifare522Result ReadValue(int block)
        {
            Mifare522Result result = ReadDataBlock(block);
            // 块数据转值
            result.Value = RFIDMifare522Utils.BlockDataToValue(result.Info); 
            return result;
        }
        #endregion

        /// <summary>
        /// 清除串口返回值数据
        /// </summary>
        private void ClearResult()
        {
            currentResult.Status = false;
            currentResult.InfoLength = 0;
            currentResult.Info = null;
            currentResult.ResponseInfo = null;
        }

        /// <summary>
        /// 获取串口数据回调
        /// </summary>
        /// <param name="data"></param>
        private void SerialPortManager_SerialPortDataReceived(string data)
        {
            byte[] resultData = RFIDMifare522Utils.HexStringToByteArray(data);

            //currentResult = new Mifare522Result();

            //判断响应状态
            //应该再判断BCC码
            if (resultData[0] == resultData.Length && resultData[2] == 0x00)
            {
                //设置状态
                currentResult.Status = true;
                //获得info长度
                currentResult.InfoLength = resultData[3];
                //获得info内容
                currentResult.Info = resultData.Skip(4).Take(currentResult.InfoLength).ToArray();
            }
            else
            {
                currentResult.Status = false;
                currentResult.InfoLength = 0;
                currentResult.Info = null;
            }
            // 返回接受的指令
            currentResult.ResponseInfo = resultData;
            requestResponse.Set();
        }
    }
}
